package com.hr.mes.service.impl;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.hr.mes.dto.Page;
import com.hr.mes.entity.Form;
import com.hr.mes.entity.FormTemplate;
import com.hr.mes.entity.User;
import com.hr.mes.enums.MyExceptionEnum;
import com.hr.mes.exception.MyException;
import com.hr.mes.mapper.FormDetailMapper;
import com.hr.mes.mapper.FormMapper;
import com.hr.mes.mapper.FormTemplateMapper;
import com.hr.mes.mapper.UserMapper;
import com.hr.mes.service.ProcessService;
import com.hr.mes.utils.PageUtil;
import com.hr.mes.vo.TaskExecuteHisVO;
import com.hr.mes.vo.TaskVO;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.Process;
import org.flowable.engine.HistoryService;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.*;

@Service
public class ProcessServiceImpl implements ProcessService {

    /**
     * 未写删除任务的api，可以这样手动执行
     * DELETE FROM ACT_RU_TASK WHERE ID_ = '0b9e86d1-676d-11f0-976a-405bd82e0ddc';
     * DELETE FROM ACT_RU_EXECUTION WHERE PROC_INST_ID_ = '0b9e86d1-676d-11f0-976a-405bd82e0ddc';
     * DELETE FROM ACT_HI_TASKINST WHERE PROC_INST_ID_ = '0b9e86d1-676d-11f0-976a-405bd82e0ddc';
     * DELETE FROM ACT_HI_PROCINST WHERE PROC_INST_ID_ = '0b9e86d1-676d-11f0-976a-405bd82e0ddc';
     */

    @Autowired
    private RepositoryService repositoryService;

    @Autowired
    private RuntimeService runtimeService;

    @Autowired
    private TaskService taskService;

    @Autowired
    private HistoryService historyService;

    @Autowired
    private FormMapper formMapper;

    @Autowired
    private FormTemplateMapper formTemplateMapper;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private FormDetailMapper formDetailMapper;

    /**
     * 根据bpmn.xml部署流程
     * @param bpmnKey
     * @return
     */
    @Override
    public String deployment(String bpmnKey) {
        //act_ge_bytearray(保存创建流程定义的资源，如bpmn.xml)、act_re_deployment(记录部署行为，相当于主表)、act_re_procdef(保存流程定义的各个节点，相当于详情表)
        String bpmn = "process/"+bpmnKey+".bpmn20.xml";
        Deployment deployment = repositoryService.createDeployment()
                .addClasspathResource(bpmn).name("首件审批流程").deploy();
        return deployment.getId();
    }

    /**
     * 删除部署
     * @param bpmnKey
     */
    @Override
    public void deleteProcessDefinition(String bpmnKey) {
        //通过流程定义key:act_re_procdef.KEY_查询流程定义act_re_procdef
        List<ProcessDefinition> processDefinitionList = repositoryService.createProcessDefinitionQuery()
                .processDefinitionKey(bpmnKey).list();
        if(!CollectionUtils.isEmpty(processDefinitionList)){
            for (ProcessDefinition processDefinition : processDefinitionList){
                //通过流程定义id:act_ru_execution.PROC_DEF_ID_查询流程实例act_ru_execution
                List<ProcessInstance> processInstanceList = runtimeService.createProcessInstanceQuery()
                        .processDefinitionId(processDefinition.getId()).list();
                if(!CollectionUtils.isEmpty(processInstanceList)){
                    //遍历流程定义对应的所有流程实例，并删除他们
                    for (ProcessInstance processInstance : processInstanceList){
                        runtimeService.deleteProcessInstance(processInstance.getId(), "");      // 第二个参数是删除原因,
                        //通过流程实例id:act_ru_execution.ID_删除流程实例act_ru_execution，
                        //同时也会删除任务表act_ru_task和流程实例的执行历史表act_hi_actinst(实测是这样)
                    }
                }
                //删除部署
                //通过部署id:act_re_procdef.DEPLOYMENT_ID_删除部署表act_re_deployment
                //同时也会删除定义流程的资源表act_ge_bytearray和流程定义表act_re_procdef(实测是这样)
                repositoryService.deleteDeployment(processDefinition.getDeploymentId(), true);
            }
        }
    }

    /**
     * 启动流程，返回任务的id
     * @param key
     * @return 任务id
     */
    @Override
    public String startProcessForService(@RequestParam String key, Integer formId){
        try {
            //通过流程定义key:act_re_procdef.KEY_启动(或者说创建)一个流程实例
            //同时也创建了一个任务:act_ru_task(实测)
            ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(key);
            //流程实例携带formId，在流程实例执行完成前这个变量都是有效的
            runtimeService.setVariableLocal(processInstance.getId(), "formId", formId);
            //通过流程实例id:act_ru_execution.PROC_INST_ID_查询刚创建的任务:act_ru_task
            Task task = taskService.createTaskQuery()
                    .processInstanceId(processInstance.getId()).singleResult();
            //任务创建人
            String employeeNumber = (String) SecurityUtils.getSubject().getPrincipal();
            task.setAssignee(employeeNumber);
            taskService.saveTask(task);
            return task.getId();
        }catch (Exception e){
            throw new MyException(MyExceptionEnum.PROCESS_START_ERROR, e);
        }
    }

    /**
     * 获取等待用户处理的任务列表，用于获取待办事项的条数
     * @return
     */
    @Override
    public List<com.hr.mes.dto.Task> getTaskList() {
        String employeeNumber = (String) SecurityUtils.getSubject().getPrincipal();
        // act_ru_task.ASSIGNEE_=userId，获取userId对应的任务列表
        List<Task> taskList = taskService.createTaskQuery()
                .taskAssignee(employeeNumber).list();
        // Task这个DTO解决taskList序列化异常的问题
        ArrayList<com.hr.mes.dto.Task> taskDTOList = new ArrayList<>();
        if(!CollectionUtils.isEmpty(taskList)){
            for (Task task : taskList){
                com.hr.mes.dto.Task taskDTO = new com.hr.mes.dto.Task(task.getId(), task.getName(), task.getAssignee());
                taskDTOList.add(taskDTO);
            }
        }
        return taskDTOList;
    }

    /**
     * 获取等待用户处理的任务page，和getTaskList的不同是：getTaskPage关联到了prod_from和basal_form_templete，用于展示任务详情
     * @param pageNo
     * @param pageSize
     * @return
     */
    @Override
    public Page<TaskVO> getTaskPage(Integer pageNo, Integer pageSize) {
        String employeeNumber = (String) SecurityUtils.getSubject().getPrincipal();
        // act_ru_task.ASSIGNEE_=userId，获取userId对应的任务列表
        List<Task> taskList = taskService.createTaskQuery()
                .taskAssignee(employeeNumber).list();
        // 封装成List<TaskVo>
        ArrayList<TaskVO> taskVOs = new ArrayList<>();
        if(!CollectionUtils.isEmpty(taskList)){
            for (Task task : taskList){
                TaskVO taskVO = new TaskVO();
                Integer formId = (Integer)runtimeService.getVariableLocal(task.getProcessInstanceId(), "formId");
                Form form = formMapper.getForm(formId);
                FormTemplate formTemplate = formTemplateMapper.getFormTemplate(form.getFormTemplateId());
                if(!ObjectUtils.isEmpty(form)){
                    taskVO.setFormId(form.getId());
                    taskVO.setRemark(form.getRemark());
                    taskVO.setSubmitTime(form.getSubmitTime());
                    taskVO.setSubmitBy(form.getSubmitBy());
                    taskVO.setStatus(form.getStatus());
                }
                if(!ObjectUtils.isEmpty(formTemplate)){
                    taskVO.setFormTemplateId(formTemplate.getId());
                    taskVO.setFormTemplateTitle(formTemplate.getTitle());
                }
                taskVO.setTaskId(task.getId());
                taskVO.setTaskName(task.getName());
                taskVO.setAssignee(task.getAssignee());
                taskVOs.add(taskVO);
            }
        }
        //封装成Page
        PageHelper.startPage(pageNo, pageSize);
        PageInfo<TaskVO> pageInfo = new PageInfo<>(taskVOs);
        Page<TaskVO> page = PageUtil.ConvertPageInfoToPage(pageInfo);
        PageHelper.clearPage();
        return page;
    }

    /**
     * 完成任务节点
     * 1.提交表单后将任务表的formKey字段设置为formId
     * 2.将任务表的assginee设置为employeeNumber，下一个节点由谁处理
     * 3.流程执行完成后，将prod_form表的status设为0，完成
     * @param formId
     * @param taskId
     * @param nextAssignee
     */
    @Override
    public void completeTask(Integer formId, String taskId, String nextAssignee) {
        Task currentTask = taskService.createTaskQuery()
                .taskId(taskId).singleResult();
        if(!ObjectUtils.isEmpty(currentTask)){
            taskService.complete(taskId);
        }
        Task nextTask = taskService.createTaskQuery()
                .processInstanceId(currentTask.getProcessInstanceId()).singleResult();
        if(!ObjectUtils.isEmpty(nextTask) && !StringUtils.isBlank(nextAssignee)){
            nextTask.setAssignee(nextAssignee);
            taskService.saveTask(nextTask);
        }else{
            //流程完成
            formMapper.updateFormStatus(formId, 0);
        }
    }

    @Override
    public List<TaskExecuteHisVO> getExecuteHistory(String taskId) {
        Task currentTask = taskService.createTaskQuery()
                .taskId(taskId).singleResult();
        List<HistoricTaskInstance> historicTaskInstances = historyService.createHistoricTaskInstanceQuery()
                .processInstanceId(currentTask.getProcessInstanceId()).orderByHistoricTaskInstanceStartTime().asc().list();
        List<TaskExecuteHisVO> taskExecuteHisVOS = new ArrayList<>();
        for(HistoricTaskInstance task : historicTaskInstances){
            TaskExecuteHisVO taskExecuteHisVO = new TaskExecuteHisVO();
            taskExecuteHisVO.setTaskId(task.getId());
            taskExecuteHisVO.setEmployeeNumber(task.getAssignee());
            taskExecuteHisVO.setFinishTime(task.getEndTime());
            taskExecuteHisVO.setProcessNodeName(task.getName());
            if(!StringUtils.isEmpty(taskExecuteHisVO.getEmployeeNumber())){
                User user = userMapper.getUserByEmployeeNumber(taskExecuteHisVO.getEmployeeNumber());
                taskExecuteHisVO.setUserName(user.getUserName());
            }
            taskExecuteHisVOS.add(taskExecuteHisVO);
        }
        return taskExecuteHisVOS;
    }

    /**
     * 回滚流程，实际上是创建一个新的流程节点
     * 给新的流程节点设置formKey:formId，assignee设置employeeNumber，回滚到的节点由谁处理
     * @param targetTaskId
     * @param employeeNumber
     * @return
     */
    @Override
    public com.hr.mes.dto.Task rollbackTask(String targetTaskId, String employeeNumber) {
        //通过任务id查询历史任务act_hi_actinst
        HistoricTaskInstance targetTask = historyService.createHistoricTaskInstanceQuery()
                .taskId(targetTaskId).singleResult();
        if(Objects.isNull(targetTask)){
            throw new MyException(MyExceptionEnum.PROCESS_ROLLBACK_ERROR1, null);
        }
        //通过历史任务的流程实例id查询当前任务act_ru_task
        Task currentTask = taskService.createTaskQuery()
                .processInstanceId(targetTask.getProcessInstanceId()).active().singleResult();
        if(Objects.isNull(currentTask)){
            throw new MyException(MyExceptionEnum.PROCESS_ROLLBACK_ERROR2, null);
        }
        if(Objects.equals(currentTask, targetTask)){
            throw new MyException(MyExceptionEnum.PROCESS_ROLLBACK_ERROR3, null);
        }
        //通过当前任务的流程定义id查询流程定义
        BpmnModel bpmnModel = repositoryService.getBpmnModel(currentTask.getProcessDefinitionId());
        Process process = bpmnModel.getProcesses().get(0);
        Collection<FlowElement> flowElements = process.getFlowElements();
        //遍历流程定义的所有节点，如果节点的id=目标任务的TASK_DEF_KEY_，返回这个节点，拿到这个节点
        FlowElement targetElement=null;
        for (FlowElement flowElement : flowElements){
            if(ObjectUtils.equals(flowElement.getId(), targetTask.getTaskDefinitionKey())){
                targetElement=flowElement;
                break;
            }
        }
        if(Objects.isNull(targetElement)){
            throw new MyException(MyExceptionEnum.PROCESS_ROLLBACK_ERROR4, null);
        }
        //如果拨回到提交表单，则删除表单，需要重新提交
        if(targetElement.getId().equalsIgnoreCase("submitForm")){
            Integer formId = (Integer)runtimeService.getVariableLocal(currentTask.getProcessInstanceId(), "formId");
            //先删除prod_form_detal，再删除prod_form
            formDetailMapper.deleteFormDetailByFormId(formId);
            formMapper.updateFormStatus(formId, -1);
            //删除流程实例相关所有数据
            runtimeService.deleteProcessInstance(currentTask.getProcessInstanceId(), "回滚到提交表单，则删除流程实例");
            historyService.deleteHistoricTaskInstance(currentTask.getProcessInstanceId());
            return new com.hr.mes.dto.Task();
        }
        Task newTargetTask = rollbackTask(currentTask, targetTask);
        newTargetTask.setAssignee(employeeNumber);
        taskService.saveTask(newTargetTask);
        Task task = taskService.createTaskQuery()
                .taskId(newTargetTask.getId()).singleResult();
        com.hr.mes.dto.Task taskDTO = new com.hr.mes.dto.Task(task.getId(), task.getName(), task.getAssignee());
        return taskDTO;
    }

    /**
     * 回退节点，清理脏数据，并返回目标任务节点
     * @param currentTask 当前任务
     * @param targetTask  目标任务(历史任务)
     * @return
     */
    private Task rollbackTask(Task currentTask, HistoricTaskInstance targetTask){
        String processInstanceId = currentTask.getProcessInstanceId();
        //回滚节点
        runtimeService.createChangeActivityStateBuilder()
                .processInstanceId(processInstanceId).moveActivityIdTo(currentTask.getTaskDefinitionKey(), targetTask.getTaskDefinitionKey()).changeState();
        //清理两个节点之间的脏数据
        clearDirtyData(currentTask, targetTask);
        //返回新的目标节点
        return taskService.createTaskQuery()
                .processInstanceId(processInstanceId).taskDefinitionKey(targetTask.getTaskDefinitionKey()).singleResult();
    }

    /**
     * 清理两个任务之间产生的脏数据
     * 正常情况下，formId应该放在variableKeys里，我将formKey设置为formId是为了方便(反正这个字段也没有被使用)
     * @param currentTask
     * @param targetTask
     */
    private void clearDirtyData(Task currentTask, HistoricTaskInstance targetTask){
        String processInstanceId = currentTask.getProcessInstanceId();
        // 通过流程实例id查询所有历史任务act_hi_actinst，包括没完成的
        List<HistoricTaskInstance> historyTaskList = historyService.createHistoricTaskInstanceQuery()
                .processInstanceId(processInstanceId).orderByHistoricTaskInstanceStartTime().desc().list();
        //获取currentTask和targetTask之间的所有历史任务，包括currentTask和targetTask
        List<HistoricTaskInstance> historyTaskListBetweenNodes = getHistoryTaskListBetweenNodes(historyTaskList, currentTask.getId(), targetTask.getId());
        List<String> taskIds = historyTaskListBetweenNodes.stream().map(HistoricTaskInstance::getId).toList();
        for (String taskId : taskIds){
            //删除currentTask和targetTask之间的所有历史任务act_hi_actinst，包括currentTask和targetTask
            historyService.deleteHistoricTaskInstance(taskId);
        }
        //删除流程实例对应的，currentTask和targetTask之间的指定数据，包括currentTask和targetTask
        //我只存了formId，我需要他，不删除
        //List<String> variableKeys = new ArrayList<>();
        //runtimeService.removeVariables(processInstanceId, variableKeys);
    }

    /**
     * 从historyTaskList中查询获取targetTask和currentTask之间的所有任务
     * @param historyTaskList 流程实例执行过的所有任务
     * @param currentTaskId   当前任务id
     * @param targetTaskId    目标任务id
     * @return
     */
    private List<HistoricTaskInstance> getHistoryTaskListBetweenNodes(List<HistoricTaskInstance> historyTaskList,
                                                                      String currentTaskId,
                                                                      String targetTaskId){
        ArrayList<HistoricTaskInstance> betweenNodes = new ArrayList<>();
        boolean startRecord=false;
        for (HistoricTaskInstance historicTaskInstance : historyTaskList){
            if(ObjectUtils.equals(historicTaskInstance.getId(), currentTaskId)){
                startRecord=true;
            }
            if(startRecord){
                betweenNodes.add(historicTaskInstance);
            }
            if(ObjectUtils.equals(historicTaskInstance.getId(), targetTaskId)){
                break;
            }
        }
        return betweenNodes;
    }
}
